"""
Created on Aug 22, 2013

@author: dewey
"""
from __future__ import division
import copy
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from geolib.geom2d import Bounds, ComplexPolygon
from mapgraphicsitems import ItemizedQGraphicsPixmapItem, GraphicsLineStringItem

class LayerFactory(object):
    '''A factory to instantiate layers. Can be combined with other LayerFactory subclasses
    to provide support for custom layer types in other modules.'''
    
    def __init__(self):
        self.__factories = []
        self.__types = ("MapFrameLayer|BitmapLayer", 
                        "MapFrameLayer|ItemizedPointLayer",
                        "MapFrameLayer|LineStringLayer",
                        "MapFrameLayer|PolygonLayer")
        
    def addFactory(self, otherLayerFactory):
        '''Add another factory to this factory to provide support for its layer types.'''
        self.__factories.append(otherLayerFactory)
        return self
    
    def newLayer(self, layerType, layerId, options={}):
        '''Instantiates a layer of type layerType with layer ID layerId, using additional options
        object to instantiate the layer.'''
        if layerType == self.__types[0]:
            return BitmapLayer(layerId)
        elif layerType == self.__types[1]:
            return ItemizedPointLayer(layerId)
        elif layerType == self.__types[2]:
            return LineStringLayer(layerId)
        elif layerType == self.__types[3]:
            return PolygonLayer(layerId)
        else:
            return self.newLayerFromChildFactory(layerType, layerId, options)
        
    def newLayerFromChildFactory(self, layerType, layerId, options):
        for childFactory in self.__factories:
            out = childFactory.newLayer(layerType, layerId, options)
            if out is not None:
                return out
    
    def layerTypes(self):
        '''Returns a list of non abstract layer types plus those of its child factories.'''
        out = list(self.__types)
        self.appendChildFactoryLayerTypes(out)
        return out
    
    def appendChildFactoryLayerTypes(self, layerTypeList):
        for childFactory in self.__factories:
            layerTypeList.extend(childFactory.layerTypes())
    

class LayerManager(QObject):
    
    def __init__(self, mapframe, layerIdPrefix="layer", startingIndex=1):
        super(LayerManager, self).__init__(mapframe)
        self.__mapframe = mapframe
        self.layers = {}
        self.layerIdList = []
        self.__layerPrefix = layerIdPrefix
        self.__nextIdSuffix = 1
        self.__startingIndex = startingIndex
    
    def startingIndex(self):
        return self.__startingIndex
    
    def frame(self):
        return self.__mapframe
    
    def layerById(self, layerId):
        return self.layers[layerId]
    
    def layerByIndex(self, index):
        return self.layerById(self.layerIdList[index])
    
    def layersBySuperType(self, typeString):
        out = []
        for layer in self.layers.values():
            if layer.isSubTypeOf(typeString):
                out.append(layer)
        return out
    
    def layersByType(self, typeString):
        out = []
        for layer in self.layers.values():
            if layer.isType(typeString):
                out.append(layer)
        return out
    
    def sortByZIndicies(self):
        self.layerIdList.sort(key=lambda layerId: self.layerById(layerId).zIndex())
    
    def updateZIndicies(self):
        for index in range(self.getCount()):
            layer = self.layerByIndex(index)
            oldIndex = layer.zIndex()
            newIndex = index + self.startingIndex()
            if oldIndex != newIndex:
                layer.setZIndex(newIndex)
                layer.updateAttributes()
    
    def updateAllAttributes(self):
        for layer in self.layers.values():
            layer.updateAttributes()
            
    def recreateAllGraphicsItems(self):
        for layer in self.layers.values():
            layer.recreateGraphicsItems()
    
    def reverseOrder(self):
        self.layerIdList.reverse()
        self.updateZIndicies()
        
    def swapLayerPositions(self, indexA, indexB):
        layerA = self.layerIdList[indexA]
        self.layerIdList[indexA] = self.layerIdList[indexB]
        self.layerIdList[indexB] = layerA
    
    def insert(self, layer, desiredIndex):
        if self.getCount() > desiredIndex and desiredIndex >= 0:
            layer.setLayerManager(self)
            for index in reversed(range(desiredIndex, self.getCount()-1)):
                self.swapLayerPositions(index, index+1)
            self.layerIdList.append(self.layerIdList[desiredIndex])
            layerId = layer.layerId()
            self.layerIdList[desiredIndex] = layerId
            self.layers[layerId] = layer
            self.updateZIndicies()
        elif desiredIndex == self.getCount():
            self.append(layer)
        else:
            raise IndexError
    
    def replace(self, layer, desiredIndex):
        self.removeByIndex(desiredIndex)
        layer.setLayerManager(self)
        self.insert(layer, desiredIndex)
        
    def append(self, layer):
        layer.setLayerManager(self)
        layerId = layer.layerId()
        self.layers[layerId] = layer
        self.layerIdList.append(layerId)
        self.updateZIndicies()
        
    def nextLayerId(self):
        out = self.__layerPrefix + str(self.__nextIdSuffix)
        self.__nextIdSuffix += 1
        return out
    
    def removeById(self, layerId, removeData=True):
        layer = self.layerById(layerId)
        if removeData:
            layer.clear()
        layer.setLayerManager(None)
        del self.layers[layerId]
        self.layerIdList.remove(layerId)
        return layer
            
    def removeByIndex(self, index, removeData=True):
        layerId = self.layerIdList[index]
        return self.removeById(layerId, removeData)
    
    def indexOf(self, layerId):
        return self.layerIdList.index(layerId)
    
    def getCount(self):
        return len(self.layerIdList)
            
    def clear(self, removeData=True):
        for layerId in self.layerIdList:
            self.removeById(layerId, removeData)

class MapFrameLayer(QObject):
    """Base class for layers that can be added to a GraphicsMapFrame.
    Layers must be added to a LayerManager or GraphicsMapFrame instance before any items will
    appear, otherwise data is silently added to the layer to be displayed when the layer
    is added to a GraphicsMapFrame or LayerManager. The layer can only be associated with
    one GraphicsMapFrame or LayerManager at a time.
    
    Use .options to store key/value pairs associated with this layer. These values will
    be saved and reloaded with modules from the filelib.mapfileIO module.
    
    Signals:
    itemContextMenuRequested(QPoint, QString, int) (global screen pos, layerId, itemId)
    itemClicked(QString, int) (layerId, itemId)"""
    
    FIELD_NAME = "_name"
    FIELD_DESCRIPTION = "_description"
    
    OPTION_NAME = "name"
    OPTION_Z_INDEX = "z_index"
    OPTION_VISIBILITY = "visible"
    OPTION_ALPHA = "alpha"
    OPTION_ICON_RES = "icon_res"
    OPTION_DATA = "data"
    OPTION_NAME_FIELD = "name_field"
    OPTION_DESC_FIELD = "description_field"
    
    OPTION_IDS_LIST = "ids_list"
    
    def __init__(self, layerId, zIndex=0, alpha=1.0, visible=True):
        super(MapFrameLayer, self).__init__()
        self.graphicsItems = {}
        self.data = {}
        self.fields = []
        self.idsList = set()
                
        self.__itemsClickable = False
        self.__itemsRightClickable = False
        self.__layerId = layerId
        self.__layerManager = None
        self.options = {}
        self.options[self.OPTION_NAME_FIELD] = self.FIELD_NAME
        self.options[self.OPTION_DESC_FIELD] = self.FIELD_DESCRIPTION
        
        self.options[self.OPTION_Z_INDEX] = zIndex
        self.options[self.OPTION_VISIBILITY] = visible
        self.options[self.OPTION_ALPHA] = alpha
        self.options[self.OPTION_ICON_RES] = None
        
        self.__sceneRect = None
        
        self.__nextId = 0
    
    def __eq__(self, other):
        return self.layerId() == other.layerId()

    def __ne__(self, other):
        return self.layerId() != other.layerId()
            
    def setLayerManager(self, manager):
        if self.__layerManager is not manager:
            if self.__layerManager is not None:
                self.clearGraphicsItems()
            self.__layerManager = manager
            if self.__layerManager is not None:
                self.__frame = self.__layerManager.frame()
                self.recreateGraphicsItems()
            else:
                self.__frame = None
            self.setParent(self.__layerManager)
    
    def setMapFrame(self, frame):
        if frame is not self.__frame:
            if self.__frame is not None:
                self.clearGraphicsItems()
            
            self.__frame = frame                
            if self.__frame is not None: 
                self.recreateGraphicsItems()
            self.setParent(self.__frame)
    
    def layerId(self):
        """Returns the unique id of this layer"""
        return self.__layerId
    
    def name(self):
        if MapFrameLayer.OPTION_NAME in self.options:
            return self.options[MapFrameLayer.OPTION_NAME]
        else:
            return None
    
    def itemName(self, itemId):
        name = self.getDatum(itemId, self.options[self.OPTION_NAME_FIELD])
        if name:
            return name
        else:
            return "Item %s" % itemId
    
    def itemDescription(self, itemId):
        name = self.getDatum(itemId, self.options[self.OPTION_DESC_FIELD])
        if name:
            return name
        else:
            return None
    
    def renameColumn(self, fieldFrom, fieldTo):
        for itemId in self.idsList:
            itemData = self.data[itemId]
            if fieldFrom in itemData:
                itemData[fieldTo] = itemData[fieldFrom]
                del itemData[fieldFrom]
        if fieldFrom in self.fields:
            index = self.fields.index(fieldFrom)
            self.fields[index] = fieldTo
    
    def type(self):
        """Returns the layer heirarchy or type of this layer
        separated by a pipe. ex: MapFrameLayer|BitmapLayer"""
        return "MapFrameLayer"
    
    def isSubTypeOf(self, typeString):
        return self.type().startswith(typeString)
    
    def isType(self, typeString):
        return self.type() == typeString
    
    def setIcon(self, iconRes):
        self.options[self.OPTION_ICON_RES] = iconRes
    
    def icon(self):
        '''Returns a QIcon instance, or none if this object has no applicable icon'''
        iconRes = self.options[self.OPTION_ICON_RES]
        if iconRes is not None:
            return QIcon(iconRes)
        else:
            return None
    
    def setName(self, name):
        self.options[MapFrameLayer.OPTION_NAME] = name
    
    def getCount(self):
        """Return the number of QGraphicsItems this layer contains"""
        return len(self.graphicsItems)
    
    def itemsClickable(self):
        return self.__itemsClickable
    
    def itemsRightClickable(self):
        return self.__itemsRightClickable
    
    def setItemsClickable(self, flag):
        self.__itemsClickable = flag
    
    def setItemsRightClickable(self, flag):
        self.__itemsRightClickable = flag
        
    def onItemClick(self, itemId):
        self.emit(SIGNAL("itemClicked(QString, int)"), QString(self.layerId()), itemId)
    
    def onItemContextMenu(self, itemId, event):
        self.emit(SIGNAL("itemContextMenuRequested(QPoint, QString, int)"), event.screenPos(), QString(self.layerId()), itemId)
    
    def addGraphicsItem(self, graphicsItem, itemId):
        """Add a graphics item to this layer with id itemId. If the map has no frame associated with it,
        the object will not be created."""
        if self.frame() is not None:
            self.idsList.add(itemId)
            self.graphicsItems[itemId] = graphicsItem
            self.frame().scene().addItem(graphicsItem)
            self.updateGraphicsItemAttributesForItem(graphicsItem)
            self.recalculateSceneRect(graphicsItem)
            
    
    def getGraphicsItem(self, itemId):
        """Return the QGraphicsItem with id itemId"""
        if self.graphicsItems.has_key(itemId):
            return self.graphicsItems[itemId]
        else:
            return None
    
    def recreateGraphicsItems(self):
        '''Recreate all graphics items by calling createGraphicsItem() for all items in
        self.idsList. Will silently do nothing if there is no mapframe.'''
        self.clearGraphicsItems()
        for itemId in self.idsList:
            self.addGraphicsItem(self.createGraphicsItem(itemId), itemId)
    
    def createGraphicsItem(self, itemId):
        '''Override to create graphics item based on itemId data.'''
        raise NotImplementedError
    
    def setDatum(self, itemId, field, data):
        if not itemId in self.data:
            self.data[itemId] = {}
        if not field in self.fields:
            self.fields.append(field)
        self.data[itemId][field] = data
    
    def getDatum(self, itemId, field):
        if not itemId in self.data:
            return None
        if not field in self.data[itemId]:
            return None
        return self.data[itemId][field]
    
    def setData(self, itemId, data):
        self.data[itemId] = data
    
    def getData(self, itemId):
        if self.data.has_key(itemId):
            return self.data[itemId]
        else:
            return None
    
    def populateFields(self):
        fields = set()
        for itemDict in self.data.values():
            for key in itemDict.keys():
                fields.add(key)
        self.fields = list(fields)
    
    def updateAttributes(self):
        """Update attributes for all items according to updateItemAttributes() and update the bounding scene rectangle"""
        rect = None
        for itemId in self.graphicsItems.keys():
            self.updateItemAttributes(itemId)
            graphicsItem = self.graphicsItems[itemId]
            if rect is None:
                rect = graphicsItem.boundingRect()
            else:
                rect = rect.united(graphicsItem.boundingRect())
        self.__seneRect = rect ;
    
    def updateGraphicsItemAttributes(self):
        for graphicsItem in self.graphicsItems.values():
            self.updateGraphicsItemAttributesForItem(graphicsItem)
            
    def updateGraphicsItemAttributesForItem(self, graphicsItem):
        """Update attributes for QGraphicsItem without recreating the object. By default
        updates ZIndex, visibility, and alpha."""
        graphicsItem.setZValue(self.zIndex())
        graphicsItem.setVisible(self.visible())
        graphicsItem.setOpacity(self.alpha())
    
    def updateItemAttributes(self, itemId):
        """Update attributes for QGraphicsItem item with id itemId.
        Override to set custom attributes and/or positions. By default calls
        updateGraphicsItemAttributesForItem()"""
        self.updateGraphicsItemAttributesForItem(self.graphicsItems[itemId])
    
    def clearGraphicsItems(self):
        for graphicsItem in self.graphicsItems.values():
            self.frame().scene().removeItem(graphicsItem)
        self.graphicsItems.clear()
        self.__sceneRect = None
    
    def clear(self):
        """Clear all QGraphicsItems from this layer"""
        self.clearGraphicsItems()
        self.data.clear()
        self.idsList.clear()
        
    def __del__(self):
        self.clear()
    
    def removeItem(self, itemId):
        """Remove the item with id itemId"""
        graphicsItem = self.graphicsItems.pop(itemId)
        self.frame().scene().removeItem(graphicsItem)
        self.idsList.remove(itemId)
        if self.data.has_key(itemId):
            self.data.pop(itemId)
        self.recalculateSceneRect()
    
    def zIndex(self):
        """Returns the Z index of this layer"""
        return self.options[self.OPTION_Z_INDEX]
    
    def setZIndex(self, zIndex):
        """Sets the Z index of all graphicsItems in this layer"""
        self.options[self.OPTION_Z_INDEX] = zIndex
    
    def manager(self):
        """Return the layer manager associated with this layer"""
        return self.__layerManager
    
    def frame(self):
        """Returns a reference to the mapframe that is the parent of this layer"""
        if self.__layerManager is not None:
            return self.__layerManager.frame()
        else:
            return None
    
    def alpha(self):
        """Returns the alpha of this layer (0-1, 1 being opaque)"""
        return self.options[self.OPTION_ALPHA]
    
    def setAlpha(self, alpha):
        """Sets the alpha of this layer (0-1, 1 being opaque)"""
        self.options[self.OPTION_ALPHA] = alpha
    
    def visible(self):
        """Returns True of this layer is visible, false otherwise"""
        return self.options[self.OPTION_VISIBILITY]
    
    def setVisible(self, flag):
        """Sets the visibility of this layer"""
        self.options[self.OPTION_VISIBILITY] = flag
    
    def nextId(self):
        """Returns the next available id for the next item in this layer"""
        self.__nextId += 1
        return self.__nextId
    
    def recalculateSceneRect(self, graphicsItem=None):
        """Recalculate the bounding scene rectangle for this layer, or update it to include
        graphicsItem.boundingRect()"""
        if graphicsItem is not None:
            if self.__sceneRect is not None:
                self.__sceneRect = self.__sceneRect.united(graphicsItem.boundingRect())
            else:
                self.__sceneRect = graphicsItem.boundingRect()
        else:
            rect = None
            for item in self.graphicsItems.values():
                if rect is None:
                    rect = item.boundingRect()
                else:
                    rect = rect.united(item.boundingRect())
            self.__sceneRect = rect
        
        return self.__sceneRect
    
    def bounds(self):
        """Returns a Bounds object containing the max/min lat/lon coordinates"""
        sceneRect = self.sceneRect()
        if sceneRect is not None:
            return self.frame().projection().xyRect(sceneRect)
        else:
            return None
    
    def zoomBounds(self):
        """Zooms to the bounds represented by this layer"""
        sceneRect = self.__sceneRect
        if sceneRect is not None:
            self.frame().zoomToSceneRect(sceneRect)
        else:
            pass
    
    def layerInformationDict(self):
        '''Returns a dictionary capable of recreating the layer.'''
        out = dict.copy()
        out[self.OPTION_IDS_LIST] = self.idsList.copy()
        out[self.OPTION_DATA] = copy.deepcopy(self.data.copy())
        return out
    
    def loadLayerInformationFrom(self, informationDict):
        '''Load layer based on a dictionary obtained from layerInformationDict(). Subclasses
        should call the superclass method first as it loads self.idsList'''
        self.setAlpha(informationDict[self.OPTION_ALPHA])
        self.setZIndex(informationDict[self.OPTION_Z_INDEX])
        self.setVisibility(informationDict[self.OPTION_VISIBILITY])
        self.setName(informationDict[self.OPTION_NAME])
        self.idsList = self.idsList[self.OPTION_IDS_LIST]       

    
class BitmapLayer(MapFrameLayer):
    """A layer containing a number of QPixmap items corresponding to lat/lon Bounds objects. Meant to be 
    extended for loading/saving purposes."""
    
    def __init__(self, layerId, zIndex=0):
        super(BitmapLayer, self).__init__(layerId, zIndex)
        self.bounds = {}
        self.pixmaps = {}
    
    def type(self):
        return "MapFrameLayer|BitmapLayer"
    
    def clear(self):
        super(BitmapLayer, self).clear()
        self.bounds = {}
        self.pixmaps = {}
    
    def addPixmapItem(self, qpixmap, bounds):
        """Add a QPixmap bounded by lat/lon Bounds bounds. Returns the item id."""
        itemId = self.nextId()
        self.bounds[itemId] = bounds
        self.pixmaps[itemId] = qpixmap
        self.idsList.add(itemId)
        if self.frame() is not None:
            graphicsItem = self.createGraphicsItem(itemId)
            self.addGraphicsItem(graphicsItem, itemId)
        return itemId
    
    def scaleAndPositionImage(self, bounds, pxmap):
        """Ok to not check for frame() because this check is performed in addPixmapItem()."""
        sceneRect = self.frame().projection().sceneRect(bounds)
        if sceneRect.width() < 0:
            newmaxX = bounds.maxx() + 360
            newminX = bounds.minx() + 360
            bounds = Bounds(newminX, newmaxX, bounds.miny(), bounds.maxy())
            sceneRect = self.frame().projection().sceneRect(bounds, True)
        currentWidth = pxmap.pixmap().width()
        currentHeight = pxmap.pixmap().height()
        scaleX = sceneRect.width() / currentWidth
        scaleY = sceneRect.height() / currentHeight
        pxmap.setPos(sceneRect.topLeft())
        pxmap.scale(scaleX, scaleY)
    
    def createGraphicsItem(self, itemId):
        bounds = self.bounds[itemId]
        qpixmap = self.pixmaps[itemId]
        
        pxmap = ItemizedQGraphicsPixmapItem(self, qpixmap)
        pxmap.setItemId(itemId)
        self.scaleAndPositionImage(bounds, pxmap)
        return pxmap
    
    def removeItem(self, itemId):
        super(BitmapLayer, self).removeItem(itemId)
        self.bounds.pop(itemId)
        self.pixmaps.pop(itemId)
        
    
class ItemizedPointLayer(MapFrameLayer):   
    """A map layer that represents a series of points, with a dictionary to store data for each item.
    
    Signals:
    itemClicked(QString, int) - emitted if setItemsClickable(true) is called first
    itemContextMenuRequested(QString, int, QGraphicsSceneContextMenuEvent) - emitted if setItemsRightClickable(true) is called first"""
    
    DEFAULT_SIZE = 20
    
    MARKER_BIND_CENTRE = (0.5, 0.5)
    MARKER_BIND_BOTTOM_LEFT = (1, 0)
    MARKER_BIND_BOTTOM_CENTRE = (0.5, 1)
    MARKER_BIND_BOTTOM_RIGHT = (1, 1)
    MARKER_BIND_RIGHT_CENTRE = (1, 0.5)
    MARKER_BIND_TOP_RIGHT = (1, 0)
    MARKER_BIND_TOP_CENTRE = (0.5, 0)
    MARKER_BIND_TOP_LEFT = (0, 0)
    MARKER_BIND_LEFT_CENTRE = (0, 0.5)
    
    OPTION_MARKER_BIND_X = "marker_bind_x"
    OPTION_MARKER_BIND_Y = "marker_bind_y"
    OPTION_MARKER_RES = "marker_resource"
    OPTION_SIZE_X = "marker_size_x"
    OPTION_SIZE_Y = "marker_size_y"
    
    OPTION_POINTS = "points"
    
    def __init__(self, layerId, zIndex=0):
        super(ItemizedPointLayer, self).__init__(layerId, zIndex)
        self.points = {}
        self.markerResources = {}
        self.markerBind = {}
        self.markerSizes = {}
        
        self.__marker = None
        self.setDefaultMarkerSize(self.DEFAULT_SIZE, self.DEFAULT_SIZE)
        self.setDefaultMarkerBind(self.MARKER_BIND_CENTRE)
    
    def type(self):
        return "MapFrameLayer|ItemizedPointLayer"
       
    def addPoint(self, latLonPoint):
        """Add a point at this location. Returns the itemId."""
        itemId = self.nextId()
        self.points[itemId] = latLonPoint
        self.idsList.add(itemId)
        if self.frame() is not None:
            graphicsItem = self.createGraphicsItem(itemId)
            self.addGraphicsItem(graphicsItem, itemId)
        return itemId
    
    def createGraphicsItem(self, itemId):
        bindX, bindY = self.bind(itemId)
        latLonPoint = self.points[itemId]
        pxMap = self.marker(itemId)
        pxMapItem = ItemizedQGraphicsPixmapItem(self, pxMap)
        pxMapItem.setOffset(-pxMap.width() * bindX, -pxMap.height() * bindY)
        size = self.markerSize(itemId)
        pxMapItem.scale(size[0] / pxMap.width(), size[1] / pxMap.height())
        pxMapItem.setFlag(QGraphicsItem.ItemIgnoresTransformations, True)

        pxMapItem.setPos(self.frame().projection().scenePoint(latLonPoint))
        pxMapItem.setItemId(itemId)
        
        return pxMapItem
    
    def setDefaultMarkerBind(self, markerBindTuple):
        """Set a tuple indicating the binding of the position to the marker.
        See MARKER_BIND_* constants above. All are in the form (fractionX, fractionY),
        with fraction relating to the width and height of the marker."""
        self.options[self.OPTION_MARKER_BIND_X] = markerBindTuple[0]
        self.options[self.OPTION_MARKER_BIND_Y] = markerBindTuple[1]
    
    def setDefaultMarkerResource(self, resource):
        """Set the filename or resource that will represent each item. Will be used in the
        QImage(str) constructor. Call before adding any items to this layer, 
        or call recreateGraphicsItems() to update all items"""
        self.options[self.OPTION_MARKER_RES] = resource
        self.__marker = QPixmap.fromImage(QImage(resource))
        self.setIcon(resource)
    
    def setMarkerResource(self, itemId, markerRes, bind=None):
        if markerRes is not None:
            self.markerResources[itemId] = markerRes
        else:
            del self.markerResources[itemId]
        
        if bind is not None:
            self.markerBind[itemId] = bind
        else:
            del self.markerBind[itemId]
    
    def marker(self, itemId):
        """Return the QPixmapItem that represents item itemId"""
        if self.markerResources.has_key(itemId):
            res = self.markerResources[itemId]
            if res is not None:
                return QPixmap.fromImage(QImage(res))
        return self.__marker
    
    def bind(self, itemId):
        if self.markerBind.has_key(itemId):
            bind = self.markerBind[itemId]
            if bind is not None:
                return bind
        return self.options[self.OPTION_MARKER_BIND_X], self.options[self.OPTION_MARKER_BIND_Y]
    
    def setDefaultMarkerSize(self, width, height):
        """Set marker size in pixels. Call before adding items to the layer, 
        or call recreateItems() to update all items"""
        self.options[self.OPTION_SIZE_X] = width
        self.options[self.OPTION_SIZE_Y] = height
    
    def setMarkerSize(self, itemId, sizeTup):
        if sizeTup is not None:
            self.markerSizes[itemId] = sizeTup
        else:
            del self.markerSizes[itemId]
    
    def markerSize(self, itemId):
        if self.markerSizes.has_key(itemId):
            size = self.markerSizes[itemId]
            if size is not None:
                return size
        return self.options[self.OPTION_SIZE_X], self.options[self.OPTION_SIZE_Y]
    
    def removeItem(self, itemId):
        super(ItemizedPointLayer, self).removeItem(itemId)
        self.points.pop(itemId)
        
    def clear(self):
        super(ItemizedPointLayer, self).clear()
        self.points = {}
    
    def layerInformationDict(self):
        out = MapFrameLayer.layerInformationDict(self)
        out[self.OPTION_POINTS] = self.points.copy()
    
    def loadLayerInformationFrom(self, informationDict):
        MapFrameLayer.loadLayerInformationFrom(self, informationDict)
        self.points = informationDict[self.OPTION_POINTS]
        self.setDefaultMarkerResource(informationDict[self.OPTION_MARKER_RES])
        
        
class LineStringLayer(MapFrameLayer):
    
    def __init__(self, layerId, zIndex=0):
        super(LineStringLayer, self).__init__(layerId, zIndex)
        self.pointSeries = {}
    
    def type(self):
        return "MapFrameLayer|LineStringLayer"
    
    def addPointSeries(self, pointSeries):
        itemId = self.nextId()
        self.pointSeries[itemId] = pointSeries
        self.idsList.add(itemId)
        if self.frame() is not None:
            graphicsItem = self.createGraphicsItem(itemId)
            self.addGraphicsItem(graphicsItem, itemId)
        return itemId
    
    def createGraphicsItem(self, itemId):
        pointSeries = self.pointSeries[itemId]
        graphicsItem = GraphicsLineStringItem()
        graphicsItem.commitAll(pointSeries, self.frame().projection())
        graphicsItem.finalize()
        graphicsItem.setItemId(itemId)
        return graphicsItem

class PolygonLayer(MapFrameLayer):
    
    def __init__(self, layerId, zIndex=0):
        super(PolygonLayer, self).__init__(layerId, zIndex)
        self.polygons = {}
    
    def type(self):
        return "MapFrameLayer|PolygonLayer"
    
    def addPolygon(self, polygon):
        itemId = self.nextId()
        self.polygons[itemId] = polygon
        self.idsList.add(itemId)
        if self.frame() is not None:
            graphicsItem = self.createGraphicsItem(itemId)
            return self.addGraphicsItem(graphicsItem, itemId)
        return itemId
    
    def createGraphicsItem(self, itemId):
        polygon = self.polygons[itemId]
        graphicsItem = GraphicsLineStringItem(True)
        graphicsItem.commitAll(polygon, self.frame().projection())
        if isinstance(polygon, ComplexPolygon):
            for interiorPolygon in polygon.interiorPolygons():
                graphicsItem.commitInterior(interiorPolygon, self.frame().projection())
        graphicsItem.setItemId(itemId)
        return graphicsItem        
        